#include "gtkintl.h"
#include "gtkbuildable.h"
#include "gtkwidgetpath.h"
+#include "gtkcssnodeprivate.h"
+#include "gtkcsscustomgadgetprivate.h"
+#include "gtkwidgetprivate.h"
+#include "gtkcontainerprivate.h"
+#include "gtkstylecontextprivate.h"
+#include "gtkcssstylepropertyprivate.h"
+#include "gtklabel.h"
#include "a11y/gtkframeaccessible.h"
*
* # CSS nodes
*
- * GtkFrame has a single CSS node with name frame. The style class
- * .flat can appear with it. In the #GtkShortcutsWindow, frames
- * are used with the .keycap style class.
+ * |[<!-- language="plain" -->
+ * frame
+ * ├── border
+ * ├── <label widget>
+ * ╰── <child>
+ * ]|
+ *
+ * GtkFrame has a main CSS node with name frame and a subnode with
+ * name border. The border node is used to render the visible border.
+ * The style class .flat can appear with the main node. In the
+ * #GtkShortcutsWindow, frames are used with the .keycap style class.
*/
/* Properties */
GtkWidget *label_widget;
+ GtkCssGadget *gadget;
+ GtkCssGadget *border_gadget;
+
gint16 shadow_type;
gfloat label_xalign;
gfloat label_yalign;
static GParamSpec *frame_props[LAST_PROP];
+static void gtk_frame_finalize (GObject *object);
static void gtk_frame_set_property (GObject *object,
guint param_id,
const GValue *value,
gint width,
gint *minimum_height,
gint *natural_height);
+static void gtk_frame_state_flags_changed (GtkWidget *widget,
+ GtkStateFlags previous_state);
+
+static void gtk_frame_measure (GtkCssGadget *gadget,
+ GtkOrientation orientation,
+ gint for_size,
+ gint *minimum_size,
+ gint *natural_size,
+ gint *minimum_baseline,
+ gint *natural_baseline,
+ gpointer data);
+static void gtk_frame_allocate (GtkCssGadget *gadget,
+ const GtkAllocation *allocation,
+ int baseline,
+ GtkAllocation *out_clip,
+ gpointer data);
+static void gtk_frame_allocate_border (GtkCssGadget *gadget,
+ const GtkAllocation *allocation,
+ int baseline,
+ GtkAllocation *out_clip,
+ gpointer data);
+static gboolean gtk_frame_render (GtkCssGadget *gadget,
+ cairo_t *cr,
+ int x,
+ int y,
+ int width,
+ int height,
+ gpointer data);
G_DEFINE_TYPE_WITH_CODE (GtkFrame, gtk_frame, GTK_TYPE_BIN,
widget_class = GTK_WIDGET_CLASS (class);
container_class = GTK_CONTAINER_CLASS (class);
+ gobject_class->finalize = gtk_frame_finalize;
gobject_class->set_property = gtk_frame_set_property;
gobject_class->get_property = gtk_frame_get_property;
widget_class->get_preferred_height = gtk_frame_get_preferred_height;
widget_class->get_preferred_height_for_width = gtk_frame_get_preferred_height_for_width;
widget_class->get_preferred_width_for_height = gtk_frame_get_preferred_width_for_height;
+ widget_class->state_flags_changed = gtk_frame_state_flags_changed;
container_class->remove = gtk_frame_remove;
container_class->forall = gtk_frame_forall;
gtk_frame_init (GtkFrame *frame)
{
GtkFramePrivate *priv;
+ GtkCssNode *widget_node;
frame->priv = gtk_frame_get_instance_private (frame);
priv = frame->priv;
priv->shadow_type = GTK_SHADOW_ETCHED_IN;
priv->label_xalign = 0.0;
priv->label_yalign = 0.5;
+
+ widget_node = gtk_widget_get_css_node (GTK_WIDGET (frame));
+ priv->gadget = gtk_css_custom_gadget_new_for_node (widget_node,
+ GTK_WIDGET (frame),
+ gtk_frame_measure,
+ gtk_frame_allocate,
+ gtk_frame_render,
+ NULL,
+ NULL);
+ priv->border_gadget = gtk_css_custom_gadget_new ("border",
+ GTK_WIDGET (frame),
+ priv->gadget,
+ NULL,
+ NULL,
+ gtk_frame_allocate_border,
+ NULL,
+ NULL,
+ NULL);
+}
+
+static void
+gtk_frame_finalize (GObject *object)
+{
+ GtkFrame *frame = GTK_FRAME (object);
+ GtkFramePrivate *priv = frame->priv;
+
+ g_clear_object (&priv->border_gadget);
+ g_clear_object (&priv->gadget);
+
+ G_OBJECT_CLASS (gtk_frame_parent_class)->finalize (object);
}
static void
case PROP_LABEL_XALIGN:
gtk_frame_set_label_align (frame, g_value_get_float (value),
priv->label_yalign);
- break;
+ break;
case PROP_LABEL_YALIGN:
gtk_frame_set_label_align (frame, priv->label_xalign,
g_value_get_float (value));
GtkShadowType type)
{
GtkFramePrivate *priv;
- GtkWidget *widget;
- GtkStyleContext *context;
g_return_if_fail (GTK_IS_FRAME (frame));
if ((GtkShadowType) priv->shadow_type != type)
{
- widget = GTK_WIDGET (frame);
priv->shadow_type = type;
- context = gtk_widget_get_style_context (GTK_WIDGET (frame));
if (type == GTK_SHADOW_NONE)
- gtk_style_context_add_class (context, GTK_STYLE_CLASS_FLAT);
+ gtk_css_gadget_add_class (priv->border_gadget, GTK_STYLE_CLASS_FLAT);
else
- gtk_style_context_remove_class (context, GTK_STYLE_CLASS_FLAT);
+ gtk_css_gadget_remove_class (priv->border_gadget, GTK_STYLE_CLASS_FLAT);
- if (gtk_widget_is_drawable (widget))
- gtk_widget_queue_draw (widget);
-
- gtk_widget_queue_resize (widget);
g_object_notify_by_pspec (G_OBJECT (frame), frame_props[PROP_SHADOW_TYPE]);
}
}
return frame->priv->shadow_type;
}
-static void
-get_padding_and_border (GtkFrame *frame,
- GtkBorder *border)
+static gboolean
+gtk_frame_draw (GtkWidget *widget,
+ cairo_t *cr)
{
- GtkStyleContext *context;
- GtkStateFlags state;
-
- context = gtk_widget_get_style_context (GTK_WIDGET (frame));
- state = gtk_style_context_get_state (context);
-
- gtk_style_context_get_padding (context, state, border);
+ gtk_css_gadget_draw (GTK_FRAME (widget)->priv->gadget, cr);
- if (frame->priv->shadow_type != GTK_SHADOW_NONE)
- {
- GtkBorder tmp;
-
- gtk_style_context_get_border (context, state, &tmp);
- border->top += tmp.top;
- border->right += tmp.right;
- border->bottom += tmp.bottom;
- border->left += tmp.left;
- }
+ return FALSE;
}
static gboolean
-gtk_frame_draw (GtkWidget *widget,
- cairo_t *cr)
+gtk_frame_render (GtkCssGadget *gadget,
+ cairo_t *cr,
+ int x,
+ int y,
+ int width,
+ int height,
+ gpointer data)
{
- GtkFrame *frame;
+ GtkWidget *widget;
GtkFramePrivate *priv;
- GtkStyleContext *context;
- gint x, y, width, height;
+ gint xc, yc, w, h;
GtkAllocation allocation;
- GtkBorder padding;
- frame = GTK_FRAME (widget);
- priv = frame->priv;
+ widget = gtk_css_gadget_get_owner (gadget);
+ priv = GTK_FRAME (widget)->priv;
+
+ cairo_save (cr);
gtk_widget_get_allocation (widget, &allocation);
- get_padding_and_border (frame, &padding);
- context = gtk_widget_get_style_context (widget);
- x = priv->child_allocation.x - allocation.x - padding.left;
- y = priv->child_allocation.y - allocation.y - padding.top;
- width = priv->child_allocation.width + padding.left + padding.right;
- height = priv->child_allocation.height + padding.top + padding.bottom;
+ /* We want to use the standard gadget drawing for the border,
+ * so we clip out the label allocation in order to get the
+ * frame gap.
+ */
+ xc = priv->label_allocation.x - allocation.x;
+ yc = y;
+ w = priv->label_allocation.width;
+ h = priv->label_allocation.height;
- if (priv->label_widget)
+ if (GTK_IS_LABEL (priv->label_widget))
{
- gfloat xalign;
- gint height_extra;
- gint x2;
-
- if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
- xalign = priv->label_xalign;
- else
- xalign = 1 - priv->label_xalign;
+ PangoRectangle ink_rect;
+
+ /* Shrink the clip area for labels, so we get unclipped
+ * frames for the yalign 0.0 and 1.0 cases.
+ */
+ pango_layout_get_pixel_extents (gtk_label_get_layout (GTK_LABEL (priv->label_widget)), &ink_rect, NULL);
+ yc += (h - ink_rect.height) / 2;
+ h = ink_rect.height;
+ }
- height_extra = MAX (0, priv->label_allocation.height - padding.top)
- - priv->label_yalign * priv->label_allocation.height;
- y -= height_extra;
- height += height_extra;
+ cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
+ cairo_rectangle (cr, xc + w, yc, - w, h);
+ cairo_set_fill_rule (cr, CAIRO_FILL_RULE_WINDING);
+ cairo_clip (cr);
- x2 = padding.left + (priv->child_allocation.width - priv->label_allocation.width - 2 * LABEL_PAD - 2 * LABEL_SIDE_PAD) * xalign + LABEL_SIDE_PAD;
+ gtk_css_gadget_draw (priv->border_gadget, cr);
- gtk_render_background (context, cr, x, y, width, height);
-
- /* If the label is completely over or under the frame we can omit the gap */
- if (priv->label_yalign == 0.0 || priv->label_yalign == 1.0)
- gtk_render_frame (context, cr, x, y, width, height);
- else
- gtk_render_frame_gap (context, cr,
- x, y, width, height,
- GTK_POS_TOP, x2,
- x2 + priv->label_allocation.width + 2 * LABEL_PAD);
- }
- else
- {
- gtk_render_background (context, cr, x, y, width, height);
- gtk_render_frame (context, cr, x, y, width, height);
- }
+ cairo_restore (cr);
GTK_WIDGET_CLASS (gtk_frame_parent_class)->draw (widget, cr);
gtk_frame_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
- GtkFrame *frame = GTK_FRAME (widget);
- GtkFramePrivate *priv = frame->priv;
- GtkBin *bin = GTK_BIN (widget);
- GtkAllocation new_allocation;
- GtkWidget *child;
+ GtkAllocation clip;
gtk_widget_set_allocation (widget, allocation);
- gtk_frame_compute_child_allocation (frame, &new_allocation);
+ gtk_css_gadget_allocate (GTK_FRAME (widget)->priv->gadget,
+ allocation,
+ gtk_widget_get_allocated_baseline (widget),
+ &clip);
- /* If the child allocation changed, that means that the frame is drawn
- * in a new place, so we must redraw the entire widget.
- */
- if (gtk_widget_get_mapped (widget) && !gdk_rectangle_equal (&priv->child_allocation, &new_allocation))
- {
- gdk_window_invalidate_rect (gtk_widget_get_window (widget), allocation, FALSE);
- }
+ gtk_widget_set_clip (widget, &clip);
+}
- child = gtk_bin_get_child (bin);
- if (child && gtk_widget_get_visible (child))
- gtk_widget_size_allocate (child, &new_allocation);
+static void
+gtk_frame_allocate (GtkCssGadget *gadget,
+ const GtkAllocation *allocation,
+ int baseline,
+ GtkAllocation *out_clip,
+ gpointer data)
+{
+ GtkWidget *widget;
+ GtkFrame *frame;
+ GtkFramePrivate *priv;
+ GtkAllocation new_allocation;
+ GtkAllocation frame_allocation;
+ gint height_extra;
+ GtkAllocation clip;
+
+ widget = gtk_css_gadget_get_owner (gadget);
+ frame = GTK_FRAME (widget);
+ priv = frame->priv;
+ gtk_frame_compute_child_allocation (frame, &new_allocation);
priv->child_allocation = new_allocation;
if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
{
- GtkBorder padding;
gint nat_width, width, height;
gfloat xalign;
- get_padding_and_border (frame, &padding);
-
if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
xalign = priv->label_xalign;
else
xalign = 1 - priv->label_xalign;
gtk_widget_get_preferred_width (priv->label_widget, NULL, &nat_width);
- width = new_allocation.width - 2 * LABEL_PAD - 2 * LABEL_SIDE_PAD;
- width = MIN (width, nat_width);
+ width = MIN (new_allocation.width, nat_width);
+ gtk_widget_get_preferred_height_for_width (priv->label_widget, width, &height, NULL);
- gtk_widget_get_preferred_height_for_width (priv->label_widget, width,
- &height, NULL);
+ priv->label_allocation.x = new_allocation.x + (new_allocation.width - width) * xalign;
+ priv->label_allocation.y = new_allocation.y - height;
+ priv->label_allocation.height = height;
+ priv->label_allocation.width = width;
+ gtk_widget_size_allocate (priv->label_widget, &priv->label_allocation);
- priv->label_allocation.x = priv->child_allocation.x + LABEL_SIDE_PAD +
- (new_allocation.width - width - 2 * LABEL_PAD - 2 * LABEL_SIDE_PAD) * xalign + LABEL_PAD;
+ height_extra = (1 - priv->label_yalign) * height;
+ }
+ else
+ height_extra = 0;
- priv->label_allocation.width = width;
+ frame_allocation.x = priv->child_allocation.x;
+ frame_allocation.y = priv->child_allocation.y - height_extra;
+ frame_allocation.width = priv->child_allocation.width;
+ frame_allocation.height = priv->child_allocation.height + height_extra;
- priv->label_allocation.y = priv->child_allocation.y - MAX (height, padding.top);
- priv->label_allocation.height = height;
+ gtk_css_gadget_allocate (priv->border_gadget,
+ &frame_allocation,
+ -1,
+ &clip);
- gtk_widget_size_allocate (priv->label_widget, &priv->label_allocation);
- }
+ gtk_container_get_children_clip (GTK_CONTAINER (widget), out_clip);
+ gdk_rectangle_union (out_clip, &clip, out_clip);
+}
+
+static void
+gtk_frame_allocate_border (GtkCssGadget *gadget,
+ const GtkAllocation *allocation,
+ int baseline,
+ GtkAllocation *out_clip,
+ gpointer data)
+{
+ GtkWidget *widget;
+ GtkWidget *child;
+
+ widget = gtk_css_gadget_get_owner (gadget);
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ if (child && gtk_widget_get_visible (child))
+ gtk_widget_size_allocate (child, (GtkAllocation *)allocation);
}
static void
GtkFramePrivate *priv = frame->priv;
GtkWidget *widget = GTK_WIDGET (frame);
GtkAllocation allocation;
- GtkBorder padding;
- gint top_margin;
- gint border_width;
+ gint border_width, height;
gtk_widget_get_allocation (widget, &allocation);
- get_padding_and_border (frame, &padding);
border_width = gtk_container_get_border_width (GTK_CONTAINER (frame));
if (priv->label_widget)
{
- gint nat_width, width, height;
+ gint nat_width, width;
gtk_widget_get_preferred_width (priv->label_widget, NULL, &nat_width);
-
- width = allocation.width;
- width -= 2 * LABEL_PAD + 2 * LABEL_SIDE_PAD;
- width -= (border_width * 2) + padding.left + padding.right;
-
- width = MIN (width, nat_width);
-
- gtk_widget_get_preferred_height_for_width (priv->label_widget, width,
- &height, NULL);
-
- top_margin = MAX (height, padding.top);
+ width = MIN (allocation.width - 2 * border_width, nat_width);
+ gtk_widget_get_preferred_height_for_width (priv->label_widget, width, &height, NULL);
}
else
- top_margin = padding.top;
+ height = 0;
+
+ child_allocation->x = allocation.x + border_width;
+ child_allocation->y = allocation.y + border_width + height;
+ child_allocation->width = MAX (1, allocation.width - 2 * border_width);
+ child_allocation->height = MAX (1, allocation.height - 2 * border_width - height);
- child_allocation->x = border_width + padding.left;
- child_allocation->y = border_width + top_margin;
- child_allocation->width = MAX (1, (gint) (allocation.width - (border_width * 2) -
- padding.left - padding.right));
- child_allocation->height = MAX (1, (gint) (allocation.height - child_allocation->y -
- border_width - padding.bottom));
- child_allocation->x += allocation.x;
- child_allocation->y += allocation.y;
}
static void
-gtk_frame_get_preferred_size (GtkWidget *widget,
- GtkOrientation orientation,
- gint *minimum_size,
- gint *natural_size)
+gtk_frame_measure (GtkCssGadget *gadget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural,
+ int *minimum_baseline,
+ int *natural_baseline,
+ gpointer data)
{
- GtkFrame *frame = GTK_FRAME (widget);
- GtkFramePrivate *priv = frame->priv;
- GtkBorder padding;
+ GtkWidget *widget;
+ GtkFrame *frame;
+ GtkFramePrivate *priv;
GtkWidget *child;
- GtkBin *bin = GTK_BIN (widget);
gint child_min, child_nat;
- gint minimum, natural;
guint border_width;
- get_padding_and_border (frame, &padding);
+ widget = gtk_css_gadget_get_owner (gadget);
+ frame = GTK_FRAME (widget);
+ priv = frame->priv;
+
border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
{
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
- gtk_widget_get_preferred_width (priv->label_widget,
- &child_min, &child_nat);
- minimum = child_min + 2 * LABEL_PAD + 2 * LABEL_SIDE_PAD;
- natural = child_nat + 2 * LABEL_PAD + 2 * LABEL_SIDE_PAD;
+ gtk_widget_get_preferred_width (priv->label_widget, &child_min, &child_nat);
+ *minimum = child_min;
+ *natural = child_nat;
}
else
{
- gtk_widget_get_preferred_height (priv->label_widget,
- &child_min, &child_nat);
- minimum = MAX (0, child_min - padding.top);
- natural = MAX (0, child_nat - padding.top);
+ if (for_size > 0)
+ gtk_widget_get_preferred_height_for_width (priv->label_widget,
+ for_size - 2 * border_width, &child_min, &child_nat);
+ else
+ gtk_widget_get_preferred_height (priv->label_widget, &child_min, &child_nat);
+
+ *minimum = child_min;
+ *natural = child_nat;
}
}
else
{
- minimum = 0;
- natural = 0;
+ *minimum = 0;
+ *natural = 0;
}
- child = gtk_bin_get_child (bin);
+ child = gtk_bin_get_child (GTK_BIN (widget));
if (child && gtk_widget_get_visible (child))
{
if (orientation == GTK_ORIENTATION_HORIZONTAL)
{
- gtk_widget_get_preferred_width (child,
- &child_min, &child_nat);
- minimum = MAX (minimum, child_min);
- natural = MAX (natural, child_nat);
+ gtk_widget_get_preferred_width (child, &child_min, &child_nat);
+ *minimum = MAX (*minimum, child_min);
+ *natural = MAX (*natural, child_nat);
}
else
{
- gtk_widget_get_preferred_height (child,
- &child_min, &child_nat);
- minimum += child_min;
- natural += child_nat;
- }
- }
+ if (for_size > 0)
+ gtk_widget_get_preferred_height_for_width (child, for_size - 2 * border_width, &child_min, &child_nat);
+ else
+ gtk_widget_get_preferred_height (child, &child_min, &child_nat);
- if (orientation == GTK_ORIENTATION_HORIZONTAL)
- {
- minimum += (border_width * 2) + padding.left + padding.right;
- natural += (border_width * 2) + padding.left + padding.right;
- }
- else
- {
- minimum += (border_width * 2) + padding.top + padding.bottom;
- natural += (border_width * 2) + padding.top + padding.bottom;
+ *minimum += child_min;
+ *natural += child_nat;
+ }
}
- *minimum_size = minimum;
- *natural_size = natural;
+ *minimum += 2 * border_width;
+ *natural += 2 * border_width;
}
static void
gtk_frame_get_preferred_width (GtkWidget *widget,
- gint *minimum_size,
- gint *natural_size)
+ gint *minimum,
+ gint *natural)
{
- gtk_frame_get_preferred_size (widget, GTK_ORIENTATION_HORIZONTAL, minimum_size, natural_size);
+ gtk_css_gadget_get_preferred_size (GTK_FRAME (widget)->priv->gadget,
+ GTK_ORIENTATION_HORIZONTAL,
+ -1,
+ minimum, natural,
+ NULL, NULL);
+}
+
+static void
+gtk_frame_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum,
+ gint *natural)
+{
+ gtk_css_gadget_get_preferred_size (GTK_FRAME (widget)->priv->gadget,
+ GTK_ORIENTATION_HORIZONTAL,
+ height,
+ minimum, natural,
+ NULL, NULL);
}
static void
gtk_frame_get_preferred_height (GtkWidget *widget,
- gint *minimum_size,
- gint *natural_size)
+ gint *minimum,
+ gint *natural)
{
- gtk_frame_get_preferred_size (widget, GTK_ORIENTATION_VERTICAL, minimum_size, natural_size);
+ gtk_css_gadget_get_preferred_size (GTK_FRAME (widget)->priv->gadget,
+ GTK_ORIENTATION_VERTICAL,
+ -1,
+ minimum, natural,
+ NULL, NULL);
}
static void
gtk_frame_get_preferred_height_for_width (GtkWidget *widget,
gint width,
- gint *minimum_height,
- gint *natural_height)
+ gint *minimum,
+ gint *natural)
{
- GtkWidget *child;
- GtkFrame *frame = GTK_FRAME (widget);
- GtkFramePrivate *priv = frame->priv;
- GtkBin *bin = GTK_BIN (widget);
- GtkBorder padding;
- gint child_min, child_nat, label_width;
- gint minimum, natural;
- guint border_width;
-
- get_padding_and_border (frame, &padding);
- border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
-
- minimum = (border_width * 2) + padding.top + padding.bottom;
- natural = (border_width * 2) + padding.top + padding.bottom;
-
- width -= (border_width * 2) + padding.left + padding.right;
- label_width = width - 2 * LABEL_PAD + 2 * LABEL_SIDE_PAD;
-
- if (priv->label_widget && gtk_widget_get_visible (priv->label_widget))
- {
- gtk_widget_get_preferred_height_for_width (priv->label_widget,
- label_width, &child_min, &child_nat);
- minimum += child_min;
- natural += child_nat;
- }
-
- child = gtk_bin_get_child (bin);
- if (child && gtk_widget_get_visible (child))
- {
- gtk_widget_get_preferred_height_for_width (child,
- width, &child_min, &child_nat);
- minimum += child_min;
- natural += child_nat;
- }
-
- *minimum_height = minimum;
- *natural_height = natural;
+ gtk_css_gadget_get_preferred_size (GTK_FRAME (widget)->priv->gadget,
+ GTK_ORIENTATION_VERTICAL,
+ width,
+ minimum, natural,
+ NULL, NULL);
}
static void
-gtk_frame_get_preferred_width_for_height (GtkWidget *widget,
- gint height,
- gint *minimum_width,
- gint *natural_width)
+gtk_frame_state_flags_changed (GtkWidget *widget,
+ GtkStateFlags previous_state)
{
- GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, minimum_width, natural_width);
-}
+ GtkCssNode *border_node;
+ border_node = gtk_css_gadget_get_node (GTK_FRAME (widget)->priv->border_gadget);
+ gtk_css_node_set_state (border_node, gtk_widget_get_state_flags (widget));
+
+ GTK_WIDGET_CLASS (gtk_frame_parent_class)->state_flags_changed (widget, previous_state);
+}